//////////////////////////////////////////////
// BufferView.h
//
//////////////////////////////////////////////

/// Defines / Macros -------------------------

#pragma once

/// Includes ---------------------------------

// nkMemory
#include "../Dll/DllDefines.h"

#include "Buffer.h"
#include "BufferCast.h"

// Standards
#include <array>
#include <vector>

/// Class ------------------------------------

namespace nkMemory
{
	template <typename T = unsigned char>
	class BufferView
	{
		public :

			// Constructor
			BufferView () noexcept
			:	_data (nullptr),
				_size (0)
			{
				// Nothing to do
			}

			BufferView (T* data, unsigned long long size) noexcept
			:	_data (data),
				_size (size)
			{
				// Nothing to do
			}

			BufferView (const Buffer& buffer) noexcept
			:	_data ((T*)buffer.getData()),
				_size (buffer.getSize() / sizeof(T))
			{
				// Nothing to do
			}

			BufferView (const Buffer& buffer, unsigned long long index, unsigned long long length = 0) noexcept
			:	_data (&((T*)buffer.getData())[index]),
				_size (buffer.getSize() / sizeof(T) - index)
			{
				// Check length
				_size = length ? std::min<unsigned long long>(_size, length) : _size ;
			}

			// Block some translation that leads to trouble
			BufferView (Buffer&& buffer) = delete ;

		public :

			// Getters
			T* getData () const
			{
				return _data ;
			}

			unsigned long long getSize () const
			{
				return _size ;
			}

			bool empty () const
			{
				return !_size ;
			}

			T& front ()
			{
				return _data[0] ;
			}

			const T& front () const
			{
				return _data[0] ;
			}

			T& back ()
			{
				return _data[_size - 1] ;
			}

			const T& back () const
			{
				return _data[_size - 1] ;
			}

			T* begin ()
			{
				return _data ;
			}

			const T* begin () const
			{
				return _data ;
			}

			T* end ()
			{
				return &_data[_size] ;
			}

			const T* end () const
			{
				return &_data[_size] ;
			}

		public :

			// Conversions
			BufferView<T> subView (unsigned long long index, unsigned long long length = 0) const
			{
				// Clamp size and length
				index = index < _size ? index : _size ;
				length = ((length > _size - index) || !length) ? _size - index : length ;

				if (!length)
					return BufferView<T>() ;

				return BufferView<T>(&_data[index], length) ;
			}

		public :

			// Operators
			T& operator[] (unsigned long long index)
			{
				return _data[index] ;
			}

			const T& operator[] (unsigned long long index) const
			{
				return _data[index] ;
			}

			BufferView<T>& operator= (const BufferView<T>& other) noexcept
			{
				_data = other._data ;
				_size = other._size ;

				return *this ;
			}

			BufferView<T>& operator= (BufferView<T>&& other) noexcept
			{
				_data = other._data ;
				_size = other._size ;

				other._data = nullptr ;
				other._size = 0 ;

				return *this ;
			}

		public :

			// Constructors templated
			template <typename U>
			BufferView (const BufferCast<U>& bufferCast) noexcept
			:	_data ((T*)bufferCast.getData()),
				_size ((bufferCast.getSize() * sizeof(U)) / sizeof(T))
			{
				// Nothing to do
			}

			BufferView (const BufferCast<T>& bufferCast) noexcept
			:	_data (bufferCast.getData()),
				_size (bufferCast.getSize())
			{
				// Nothing to do
			}

			template <typename U>
			BufferView (const BufferView<U>& view) noexcept
			:	_data ((T*)view.getData()),
				_size ((view.getSize() * sizeof(U)) / sizeof(T))
			{
				// Nothing to do
			}

			BufferView (const BufferView<T>& view) noexcept
			:	_data (view._data),
				_size (view._size)
			{
				// Nothing to do
			}

			BufferView (BufferView<T>&& other) noexcept
			:	_data (other._data),
				_size (other._size)
			{
				// Reset other to flag the move
				other._data = nullptr ;
				other._size = 0 ;
			}

			template <typename U, std::size_t S>
			BufferView (std::array<U, S>& array) noexcept
			:	_data ((T*)array.data()),
				_size ((array.size() * sizeof(U)) / sizeof(T))
			{
				// Nothing to do
			}

			template <std::size_t S>
			BufferView (std::array<T, S>& array) noexcept
			:	_data (array.data()),
				_size (array.size())
			{
				// Nothing to do
			}

			template <typename U>
			BufferView (std::vector<U>& vec) noexcept
			:	_data ((T*)vec.data()),
				_size ((vec.size() * sizeof(U)) / sizeof(T))
			{
				// Nothing to do
			}

			BufferView (std::vector<T>& vec) noexcept
			:	_data (vec.data()),
				_size (vec.size())
			{
				// Nothing to do
			}

			template <typename U, std::size_t S, typename = std::enable_if<std::is_const<T>::value>>
			BufferView (const std::array<U, S>& array) noexcept
			:	_data (array.data()),
				_size ((array.size() * sizeof(U)) / sizeof(T))
			{
				// Nothing to do
			}

			template <std::size_t S, typename = std::enable_if<std::is_const<T>::value>>
			BufferView (const std::array<typename std::remove_const<T>::type, S>& array) noexcept
			:	_data (array.data()),
				_size (array.size())
			{
				// Nothing to do
			}

			template <typename U, typename = std::enable_if<std::is_const<T>::value>>
			BufferView (const std::vector<U>& vec) noexcept
			:	_data (vec.data()),
				_size ((vec.size() * sizeof(U)) / sizeof(T))
			{
				// Nothing to do
			}

			template <typename = std::enable_if<std::is_const<T>::value>>
			BufferView (const std::vector<typename std::remove_const<T>::type>& vec) noexcept
			:	_data (vec.data()),
				_size (vec.size())
			{
				// Nothing to do
			}

		public :

			// Conversion operators
			operator Buffer () const
			{
				return nkMemory::Buffer((unsigned char*)_data, _size * sizeof(T)) ;
			}

			template <typename U = T>
			operator BufferCast<U> () const
			{
				return nkMemory::BufferCast<U>((U*)_data, _size) ;
			}

		private :

			// Attributes
			T* _data ;
			unsigned long long _size ;
	} ;
}